/**
 *    Copyright (C) 2008, 2009  Victor Smirnov <aist11@gmail.com>
 *
 *    This program is free software; you can redistribute it and/or modify
 *    it under the terms of the GNU General Public License as published by
 *    the Free Software Foundation; either version 3 of the License, or
 *    (at your option) any later version.
 *
 *    This program is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *    GNU General Public License for more details.
 *
 *    You should have received a copy of the GNU General Public License along
 *    with this program; if not, write to the Free Software Foundation, Inc.,
 *    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 *    See LICENSE.exceptions in the root folder of this project for possible
 *    exceptions
 *
 */

package ru.teleology.cubefs.editor.model;

import java.util.ArrayList;
import java.util.EventListener;
import java.util.HashMap;
import java.util.List;

import javax.swing.event.EventListenerList;
import javax.swing.event.TreeModelListener;
import javax.swing.tree.TreeModel;
import javax.swing.tree.TreePath;

import ru.teleology.cubefs.api.models.Collection;
import ru.teleology.cubefs.api.models.Model;
import ru.teleology.cubefs.api.models.Container;
import ru.teleology.cubefs.api.models.Page;
import ru.teleology.cubefs.api.models.TreeMap;
import ru.teleology.cubefs.api.models.Map.Pair;
import ru.teleology.cubefs.api.reflection.Typed;
import ru.teleology.cubefs.api.reflection.number.Number;
import ru.teleology.cubefs.api.reflection.metadata.ContainerMetadata;
import ru.teleology.cubefs.api.reflection.metadata.ModelMetadata;
import ru.teleology.cubefs.api.reflection.metadata.PageMetadata;
import ru.teleology.cubefs.api.reflection.value.TypedIDValue;

public class ContainerTreeModel implements TreeModel {
	
	protected RootsList  		roots;
	protected ArrayTreeNode	rootsNode;
    protected Pair 			pairHolder;
	
    protected java.util.Map<Integer, TreePageMetadataHelper> metadataHelpers = new HashMap<Integer, TreePageMetadataHelper>();
	
    protected EventListenerList listenerList = new EventListenerList();
	private final Container container;
    
    public ContainerTreeModel(Container container) {
		this.container = container;
		
		long t0 = System.currentTimeMillis();
		for (int c = 0; c < 1; c++) buildMetadata(container.GetMetadata());
		System.err.println(System.currentTimeMillis() - t0);
		
		roots = new RootsList(container);
		
		rootsNode = new ArrayTreeNode(new RootsNodeProvider(roots));
    }
    
    private void buildMetadata(ContainerMetadata metadata) {
		for (int c = 0; c < metadata.Size(); c++) {
			ModelMetadata model = (ModelMetadata) metadata.GetItem(c);
			buildMetadata(model);
		}
	}
    
    private void buildMetadata(ModelMetadata metadata) {
		for (int c = 0; c < metadata.Size(); c++) {
			PageMetadata page = (PageMetadata) metadata.GetItem(c);
			metadataHelpers.put(page.Hash(), new TreePageMetadataHelper(page));
		}
	}
    
    public TreePageMetadataHelper getMetadata(Page page) {
    	return metadataHelpers.get(page.GetPageTypeHash());
    }

	public <T extends EventListener> T[] getListeners(Class<T> listenerType) {
		return listenerList.getListeners(listenerType);
	}

    public void addTreeModelListener(TreeModelListener l) {
        listenerList.add(TreeModelListener.class, l);
    }

    public void removeTreeModelListener(TreeModelListener l) {
        listenerList.remove(TreeModelListener.class, l);
    }

	@Override
	public Object getChild(Object parent, int index) {
		if (parent instanceof Container) {
			if (index == 0) {
				return roots.getRoots();
			}
			else {
				return rootsNode;
			}
		}
		else if (parent instanceof TreeMap) {
			TreeMap map = (TreeMap)parent;
			return getNodeChild(map, index);
		}
		else if (parent instanceof ArrayTreeNode) {
			ArrayTreeNode node = (ArrayTreeNode)parent;
			return node.getChild(index);
		}
		else if (parent instanceof Page) {
			Page page = (Page)parent;
			return getNodeChild(page, index);
		}
		else {
			return null;
		}
	}

	private Object getNodeChild(Page page, int index) {
		TreePageMetadataHelper meta = getMetadata(page);
		int size = meta.getChildrenCount(page); 
		if (index < size) {
			TypedIDValue id = (TypedIDValue) meta.getData(page, 0, index);
			return roots.getContainer().GetManager().get(id);
		}
		else {
			return -1;
		}
	}

	private Object getNodeChild(final TreeMap map, int index) {
		if (index == 0) {
			TypedIDValue root = map.GetRootID();
			if (root != null) {
				return roots.getContainer().GetManager().get(root);
			}
			else {
				return null;
			}
		}
		else {
			return new ArrayTreeNode(new TreeMapLeafProvider(map));
		}
	}

	@Override
	public int getChildCount(Object parent) {
		if (parent instanceof Container) {
			if (roots.getRoots().GetRootID() == null) {
				return 0;
			}
			else {
				return 2;
			}
		}
		else if (parent instanceof TreeMap) {
			Collection map = (Collection) parent;
			return (int) map.GetSize() == 0 ? 0 : 2;
		}
		else if (parent instanceof Collection) {
			Collection map = (Collection) parent;
			return (int) map.GetSize() == 0 ? 0 : 1;
		}
		else if (parent instanceof ArrayTreeNode) {
			ArrayTreeNode node = (ArrayTreeNode) parent;
			return (int) node.getChildrenCount();
		}
		else if (parent instanceof Page) {
			Page page = (Page)parent;
			TreePageMetadataHelper meta = getMetadata(page);
			return meta.getChildrenCount(page);
		}
		else {
			return 0;
		}
	}

	@Override
	public int getIndexOfChild(Object parent, Object child) {
		if (parent instanceof Container) {
			if (child instanceof ArrayTreeNode) {
				return 1;
			}
			else {
				return 0;
			}
		}
		else if (parent instanceof TreeMap && child instanceof ArrayTreeNode) {
			return 1;
		}
		else if (parent instanceof Model && child instanceof Page) {
			return 0;
		}
		else if (parent instanceof ArrayTreeNode && child instanceof ArrayTreeNode) {
			ArrayTreeNode pnode = (ArrayTreeNode) parent;
			ArrayTreeNode cnode = (ArrayTreeNode) child;
			return pnode.getChildIndex(cnode);
		}
		else if (parent instanceof ArrayTreeNode) {
			ArrayTreeNode pnode = (ArrayTreeNode) parent;
			long index = pnode.getProvider().getLeafIndex(child);
			
			return (int) (index - pnode.getFrom());
		}
		else if (parent instanceof Page && child instanceof Page) {
			Page prnt = (Page)parent;
			Page chld = (Page)child;
			
			return getChildIndex(prnt, chld.GetId());
		}
		else if (parent instanceof TreeMap && child instanceof Pair) {
			TreeMap prnt = (TreeMap)parent;
			Pair chld = (Pair)child;
			return (int)prnt.GetKeyIndex(chld.key());
		}
		else {
			return -1;
		}
	}

	private int getChildIndex(Page page, TypedIDValue id) {
		TreePageMetadataHelper meta = getMetadata(page);
		int size = meta.getChildrenCount(page);
		for (int c = 0; c < size; c++) {
			Typed value = meta.getDataValue(page, c);
			if (value.equals(id)) {
				return c;
			}
		}
		
		return -1;
	}

	@Override
	public Object getRoot() {
		return container;
	}

	@Override
	public boolean isLeaf(Object node) {
		if (node instanceof Page) {
			Page page = (Page)node;
			return getMetadata(page).isLeaf(page);
		}
		else if (node instanceof Collection) {
			Collection map = (Collection) node;
			return map.GetSize() == 0;
		}
		else if (node instanceof ArrayTreeNode) {
			ArrayTreeNode anode = (ArrayTreeNode) node;
			return anode.levels() == 1;
		}
		else if (node instanceof Container) {
			return roots.getRoots().GetRootID() == null;
		}
		else {
			return true;
		}
	}
	
	private List<Object> getModelPath(long index) {
		List<Object> list = new ArrayList<Object>();
		
		list.add(container);
		
		ArrayTreeNode node = rootsNode;
		list.add(rootsNode);
		
		while (!node.isLeaf()) {
			node = (ArrayTreeNode) node.getChildForLeaf(index);
			list.add(node);
		}
		
		list.add(node.getChildForLeaf(index));
		
		return list;
	}
	
	public Object[] getPagePath(TypedIDValue id) {
		List<Object> path = new ArrayList<Object>();
		
		while (!id.IsNull()) {
			Page page = container.GetManager().get(id);
			path.add(page);
			id = getMetadata(page).getParentID(page);
		}
		
		Page root = (Page) path.get(path.size() - 1);
		Number name = getMetadata(root).getModelName(root);
		
		if (name.GetBigIntValue() > 0) {

			long rootIdx = container.roots().GetKeyIndex(name);

			List<Object> modelPath = getModelPath(rootIdx);

			for (int c = path.size() - 1; c >= 0; c--) {
				modelPath.add(path.get(c));
			}
			
			return modelPath.toArray();
		}
		else {
			Object[] path0 = new Object[path.size() + 2];
			path0[0] = container;
			path0[1] = container.roots();
			
			for (int c = 0; c < path.size(); c++) {
				path0[c + 2] = path.get(path.size() - c - 1);
			}
			
			return path0;
		}
	}
	
	
	public Object[] getKeyPath(Typed key, TypedIDValue id) {
		
		Page page = null;
		while (!id.IsNull()) {
			page = container.GetManager().get(id);
			id = getMetadata(page).getParentID(page);
		}
		
		Number name = getMetadata(page).getModelName(page);
		
		List<Object> modelPath;
		
		if (name.GetBigIntValue() > 0) 
		{
			long rootIdx = container.roots().GetKeyIndex(name);
			modelPath = getModelPath(rootIdx);
		}
		else {
			modelPath = new ArrayList<Object>();

			modelPath.add(container);
			modelPath.add(container.roots());
		}
		
		return getKeyPath(modelPath, key).toArray();
	}

	private List<Object> getKeyPath(List<Object> path, Typed key) {
		TreeMap map = (TreeMap) path.get(path.size() - 1);
		
		ArrayTreeNode node = new ArrayTreeNode(new TreeMapLeafProvider(map));
		path.add(node);
		
		long index = map.GetKeyIndex(key);
		
		while (!node.isLeaf()) {
			node = (ArrayTreeNode) node.getChildForLeaf(index);
			path.add(node);
		}
		
		path.add(node.getChildForLeaf(index));
		
		return path;
	}

	@Override
	public void valueForPathChanged(TreePath path, Object newValue) {
		
	}
}